﻿<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>HTML5 Canvas粒子数字时钟特效 - A5源码</title>

<style>
      * {
	margin:0;
	outline:none;
	padding:0;
}
body {
	background:#000;
	font-family:'Lucida Grande','Helvetica','Arial';
	font-size:10px;
	overflow:hidden;
}
canvas {
	background:#222;
	cursor:default;
	z-index:1;
}
.nope {
	color:#fff;
	text-align:center;
	margin-top:150px;
}
header {
	position:relative;
	text-shadow:1px 1px 0px rgba(0,0,0,0.5);
	text-transform:uppercase;
	width:100%;
	z-index:10;
}
#about {
	color:#fff;
	color:rgba(255,255,255,0.5);
	display:block;
	float:right;
	margin:20px;
	text-align:right;
	width:50%;
}
h1 {
	color:rgba(255,255,255,0.75);
	float:left;
	font-size:10px;
	font-weight:normal;
	margin:20px;
}
a {
	color:rgba(255,255,255,0.5);
	display:inline-block;
	text-decoration:none;
	transition:0.5s ease color;
	-moz-transition:0.5s ease color;
	-o-transition:0.5s ease color;
	-webkit-transition:0.5s ease color;
}
a:hover {
	color:rgba(255,255,255,0.75);
}
ul#options {
	list-style:none;
	margin:10px 0 0;
	position:relative;
	right:0;
	z-index:10;
}
ul#options li {
	margin:5px 0;
	min-width:200px;
	opacity:0;
	transition:0.25s ease-in opacity;
	-moz-transition:0.25s ease-in opacity;
	-o-transition:0.25s ease-in opacity;
	-webkit-transition:0.25s ease-in opacity;
}
ul#options li.group {
	margin-top:15px;
}
ul#options li * {
	display:none;
}
ul#options li a {
	box-shadow:1px 1px 3px rgba(0,0,0,0.25);
	background-color:rgba(0,0,0,0.5);
	border-radius:3px;
	padding:3px 5px;
	position:relative;
	transition:0.5s ease all;
	-moz-border-radius:3px;
	-o-box-shadow:1px 1px 3px rgba(0,0,0,0.25);
	-moz-box-shadow:1px 1px 3px rgba(0,0,0,0.25);
	-webkit-box-shadow:1px 1px 3px rgba(0,0,0,0.25);
	-moz-transition:0.5s ease all;
	-o-transition:0.5s ease all;
	-webkit-transition:0.5s ease all;
}
ul#options li a:hover {
	color:rgba(255,255,255,0.75);
}
ul#options li a.on {
	background-color:rgba(255,255,255,0.8);
	color:rgba(0,0,0,0.9);
	text-shadow:0px 0px 0px;
}
ul#options li a.on:after {
	content:"\2713 ";
}
ul#options.on li {
	opacity:1;
	right:20px;
}
ul#options.on li * {
	display:inline-block;
}
ul#borders {
	}ul#borders li {
	position:fixed;
	list-style:none;
	margin:0;
	background-color:transparent;
	background-color:rgba(0,0,0,0.05);
	z-index:100;
}
li#top {
	height:10px;
	left:0;
	right:0;
	top:0;
}
li#right {
	bottom:10px;
	right:0;
	top:10px;
	width:10px;
}
li#bottom {
	bottom:0;
	height:10px;
	left:0;
	right:0;
}
li#left {
	bottom:10px;
	left:0;
	top:10px;
	width:10px;
}
</style>
</head>
<body>
<canvas width="800" height="400" id="canvas"><p class="nope">No canvas, no particles</p></canvas>
<header>
    <h1>Particle Clock</h1>
    <div id="about">
        <a href="#" id="toggle-options"></a>
        <ul id="options">
            <li><a href="#" id="quivers" class="">Quiver</a></li>
            <li><a href="#" id="gradient" class="on">Gradient</a></li>
            <li><a href="#" id="color" class="on">Colorize</a></li>
            <li><a href="#" id="valentineify" class="">Valentine-ify</a></li>
            <li class="group"><span>Mouse down: explode and repel</span></li>
            <li><span>Mouse down + shift: explode and attract</span></li>
            <li><span>Arrow Up: increase particle size</span></li>
            <li class="group"><span>Sorry about your CPU</span></li>
            <li><span id="fps"></span></li>
        </ul>
    </div>
</header><script>
var Clock = (function() {
        
    // private variables
    var canvas, // canvas element
        ctx, // canvas context
        bgGrad = true, // background gradient flag
        gradient, // gradient (background)
        height = 400, // canvas height
        key = {up: false, shift: false}, // key presses
        particles = [], // particle array
        particleColor = 'hsla(0, 0%, 100%, 0.3)', // particle color
        mouse = {x: 0, y: 0}, // position of mouse / touch
        press = false, // pressed flag
        quiver = false, // quiver flag
        text, // the text to copy pixels from
        textSize = 140, // (initial) textsize
        valentine = false, // valentine-ify it for a bit?
        msgTime = 100, // time to show a message before returning to clock
        updateColor = true, // update color of gradient / particles with time?
        width = 800; // canvas width
    
    // Constants
    var FRAME_RATE = 20, // frames per second target
        MIN_WIDTH = 800, // minimum width of canvas
        MIN_HEIGHT = 400, // minimum height of canvas
        PARTICLE_NUM = 600, // (max) number of particles to generate
        RADIUS = Math.PI * 2; // radius of particle
    
    var defaultStyles = function() {
        textSize = 140;
        // particle color
        particleColor = 'hsla(0, 0%, 100%, 0.3)'; 

        // color stops
        var gradientStops = { 
            0: '#333333',
            0.5: '#222222'
        };

        // create gradient
        setGradient(gradientStops);
    };
    
    var draw = function(p) {
        ctx.fillStyle = particleColor;
        ctx.beginPath();
        ctx.arc(p.x, p.y, p.size, 0, RADIUS, true);
        ctx.closePath();
        ctx.fill();
    };
    
    var explode = function() {
        for(var i = 0, l = particles.length; i < l; i++) {
            var p = particles[i];

            if(p.inText) {

                var ax = mouse.x - p.px,
                ay = mouse.y - p.py,
                angle = Math.atan2(ay, ax),
                polarity,
                C = Math.cos(angle),
                S = Math.sin(angle);

                // change polarity
                // attract particles if mouse pressed, repel if shift + mousedown
                polarity = (key.shift === true) ? -1 : 1;

                p.x += polarity * (Math.pow((C-1), 2) -1) + p.velocityX * p.delta;
                p.y += polarity * (Math.pow((S-1), 2) -1) + p.velocityY * p.delta;

                // set previous positions
                p.px = p.x;
                p.py = p.y;

                draw(p);
            }
        }
    };

    var getTime = function(amPM) {
        var date = new Date(),
            hours = date.getHours(),
            timeOfDay = '';

        if(amPM) {
            hours = ( hours > 12 ) ? hours -= 12 : hours;
            hours = ( hours == 0 ) ? 12 : hours;
        } else {
            hours = pad(hours);
        }

        var minutes = pad(date.getMinutes());
        var seconds = pad(date.getSeconds());
        return {
            hours: hours,
            minutes: minutes,
            seconds: seconds,
            timeString: hours + " : " + minutes + " : " + seconds
        };
    };

    // animation loop
    var loop = function() {
      
        // clear out text
        ctx.clearRect(0, 0, width, height);

        var time = getTime(true);

        textSize = 140;

        // draw text on canvas
        if(valentine === true) {
            if(msgTime > 0) {
                textSize = 180;
                text = '?';
                msgTime--;
            } else {
                text = time.timeString;
            }
            // valentine-ify it by setting hue to pink
            setStyles(300);

        } else if(updateColor === true && bgGrad === true) {
            // changing color with time
            // @TODO: come up with something better, this is a hacky implementation
            var color = time.hours + time.minutes + time.seconds;
            setStyles(color);
            text = time.timeString;
        } else {
            defaultStyles();
            text = time.timeString;
        }
      
        ctx.fillStyle = "rgb(255, 255, 255)";
        ctx.textBaseline = "middle";
        ctx.font = textSize + "px 'Avenir', 'Helvetica Neue', 'Arial', 'sans-serif'";
        ctx.fillText(text, (width - ctx.measureText(text).width) * 0.5, height * 0.5);

        // copy pixels
        var imgData = ctx.getImageData(0, 0, width, height);
      
        // clear canvas, again
        ctx.clearRect(0, 0, width, height);

        if(bgGrad === true) {
            // draw gradient
            ctx.fillStyle = gradient;
            ctx.fillRect(0, 0, width, height);
        }

        if(press === false) {
            // reset particles
            for(var i = 0, l = particles.length; i < l; i++) {
                var p = particles[i];
                p.inText = false;
            }
            particleText(imgData);
        } else {
            explode();
        }
        FPS.update('fps');
    };

    var pad = function(number) {
        return ('0' + number).substr(-2);
    };

    var particleText = function(imgData) {

        var pxls = [];
        for(var w = width; w > 0; w-=6) {
            for(var h = 0; h < width; h+=6) {
                var index = (w+h*(width))*4;
                if(imgData.data[index] > 10) {
                    pxls.push([w, h]);
                }
            }
        }

        var count = pxls.length;
        for(var i = 0; i < pxls.length && i < particles.length; i++) {
            try {
                var p = particles[i], 
                    X, 
                    Y;
                
                if(quiver) {
                    X = (pxls[count-1][0]) - (p.px + Math.random() * 5);
                    Y = (pxls[count-1][1]) - (p.py + Math.random() * 5);
                } else {
                    X = (pxls[count-1][0]) - p.px;
                    Y = (pxls[count-1][1]) - p.py;
                }
          
                // tangent
                var T = Math.sqrt(X*X + Y*Y);

                // arctangent
                var A = Math.atan2(Y, X);
              
                // cosine
                var C = Math.cos(A);
              
                // sine
                var S = Math.sin(A);
              
                // set new postition
                p.x = p.px + C * T * p.delta;
                p.y = p.py + S * T * p.delta;
              
                // set previous positions
                p.px = p.x;
                p.py = p.y;
          
                p.inText = true;
          
                // draw the particle
                draw(p);
          
                if(key.up === true) {
                    p.size += 0.3;
                } else {
                    var newSize = p.size - 0.5;
                    if(newSize > p.origSize && newSize > 0) {
                        p.size = newSize;
                    } else {
                        p.size = m.origSize;
                    }
                }
            } catch(e) {
                //console.log(e);
            }
            count--;
        }
    };

    var setCoordinates = function(e) {
        if(e.offsetX) {
            return { x: e.offsetX, y: e.offsetY }; // use offset if available
        } else if (e.layerX) {
            return { x: e.layerX, y: e.layerY }; // firefox... make sure to position the canvas
        } else {
            // iOS. Maybe others too?
            return { x: e.pageX - canvas.offsetLeft, y: e.pageY - canvas.offsetTop };
        }
    };

    // set dimensions of canvas
    var setDimensions = function () {
        width = Math.max(window.innerWidth, MIN_WIDTH);
        height = Math.max(window.innerHeight, MIN_HEIGHT);

        // Resize the canvas
        canvas.width = width;
        canvas.height = height;

        canvas.style.position = 'absolute';
        canvas.style.left = '0px';
        canvas.style.top = '0px';
    };

    var setGradient = function(gradientStops) {
      
        // create gradient
        gradient = ctx.createRadialGradient(width / 2, height / 2, 0, width / 2, height / 2, width);
      
        // iterate through colorstops
        for (var position in gradientStops) {
            var color = gradientStops[position];
            gradient.addColorStop(position, color);
        }
    };

    var setStyles = function(hue) {
        // color stops
        var gradientStops = { 
            0: 'hsl(' + hue + ', 100%, 100%)',
            0.5: 'hsl(' + hue +', 10%, 50%)'
        };

        // change particle color
        particleColor = 'hsla(' + hue + ', 10%, 50%, 0.3)';

        // create gradient
        setGradient(gradientStops);
    };

    /** 
     * Public Methods
     */
    return {

        init: function(canvasID) {
        
            canvas = document.getElementById(canvasID);
            // make sure canvas exists and that the browser understands it
            if(canvas === null || !canvas.getContext) {
                return;
            }
            // set context
            ctx = canvas.getContext("2d");
      
            // set dimensions
            setDimensions();
        
            // ui
            this.ui();
        
            for(var i = 0; i < PARTICLE_NUM; i++) {
                particles[i] = new Particle(canvas);
            }   
        
            // show FPS
            FPS.initialize(canvas, 'fps');
        
            // set defaults
            defaultStyles();
        
            // let's do this
            setInterval(loop, FRAME_RATE);
        
        },
      
        ui: function() {
        
            // UI: buttons and events
            var toggleOptions = document.getElementById('toggle-options'),
                options = document.getElementById('options'),
                onMsg = '[-] Hide Options',
                offMsg = '[+] Show Options',
                quiverBtn = document.getElementById('quivers'),
                gradientBtn = document.getElementById('gradient'),
                valentineifyBtn = document.getElementById('valentineify'),
                colorBtn = document.getElementById('color');
        
            toggleOptions.innerHTML = offMsg;
        
            /**
             * Events
             */
            toggleOptions.addEventListener('click', function(e) {
                e.preventDefault();
                if(options.className === 'on') {
                    options.className = '';
                    toggleOptions.innerHTML = offMsg;
                } else {
                    options.className = 'on';
                    toggleOptions.innerHTML = onMsg;
                }
            }, false);
        
            quiverBtn.addEventListener('click', function(e) {
                e.preventDefault();
                if(quiverBtn.className === 'on') {
                    quiverBtn.className = '';
                    quiver = false;
                } else {
                    quiverBtn.className = 'on';
                    quiver = true;
                }
            }, false);
        
            gradientBtn.addEventListener('click', function(e) {
                e.preventDefault();
                if(gradientBtn.className === 'on') {
                    gradientBtn.className = '';
                    bgGrad = false;
                } else {
                    gradientBtn.className = 'on';
                    bgGrad = true;
                }
            }, false);
        
            valentineifyBtn.addEventListener('click', function(e) {
                e.preventDefault();
                if(valentineifyBtn.className === 'on') {
                    valentineifyBtn.className = '';
                    valentine = false;
                    msgTime = 0;
                } else {
                    valentineifyBtn.className = 'on';
                    msgTime = 60;
                    valentine = true;
                }
            }, false);
        
            colorBtn.addEventListener('click', function(e) {
                e.preventDefault();
                if(colorBtn.className === 'on') {
                    colorBtn.className = '';
                    updateColor = false;
                } else {
                    colorBtn.className = 'on';
                    updateColor = true;
                }
            }, false);
        
            document.addEventListener('keydown', function(e) {
                switch(e.keyCode ) {
                    case 16: // shift
                        key.shift = true;
                        e.preventDefault();
                        break;
                    case 38: // up key
                        key.up = true;
                        e.preventDefault();
                        break;
                }
            }, false);
        
            document.addEventListener('keyup', function(e) {
                switch(e.keyCode ) {
                    case 16: // shift
                        key.shift = false;
                        e.preventDefault();
                        break;
                    case 38: // space
                        key.up = false;
                        e.preventDefault();
                        break;
                }
            }, false);

            window.addEventListener('resize', function(e){
                setDimensions();
            }, false);
        
            canvas.addEventListener('mousedown', function(e){
                press = true;
            }, false);

            document.addEventListener('mouseup', function(e){
                press = false;
            }, false);
        
            canvas.addEventListener('mousemove', function(e) {
                if(press) {
                    mouse = setCoordinates(e);
                }
            }, false);
        
            // @TODO: add touch events
        
        }
      
    };
    
  })();
  
  // Create new particles
  var Particle = function(canvas) {
  
        var range = Math.random() * 180 / Math.PI, // random starting point
            spread = canvas.height, // how far away from text should the particles begin?
            size = Math.random() * 7; // random size of particle
    
        this.delta = 0.25;
        this.x = 0;
        this.y = 0;
    
        // starting positions
        this.px = (canvas.width / 2) + (Math.cos(range) * spread);
        this.py = (canvas.height / 2) + (Math.sin(range) * spread);
    
        this.velocityX = Math.floor(Math.random() * 10) - 5;
        this.velocityY = Math.floor(Math.random() * 10) - 5;
    
        this.size  = size;
        this.origSize = size;
    
        this.inText = false;
    
  };
  
  var FPS = {

    // defaults
    delta: 0,
    lastTime: 0,
    frames: 0,
    totalTime: 0,
    updateTime: 0,
    updateFrames: 0,

    initialize: function(canvasID, fpsID) {
        this.lastTime = (new Date()).getTime();
        if(!document.getElementById(fpsID) && document.getElementById(canvasID)) {
            this.createFPS(canvasID, fpsID);
        }
    },

    // create FPS div if needed
    createFPS: function(canvasID, fpsID) {
        var div = document.createElement('div');
        div.setAttribute('id', fpsID);
        var canvas = document.getElementById(canvasID);
        var parent = canvas.parentNode;
        div.innerHTML = "FPS AVG: 0 CURRENT: 0";
        parent.appendChild(div);
    },

    // update FPS count
    update: function(fpsID) {    
        var now = (new Date()).getTime();
        this.delta = now - this.lastTime;
        this.lastTime = now;
        this.updateTime += this.delta;
        this.totalTime += this.delta;
        this.frames++;
        this.updateFrames++;
        document.getElementById(fpsID).innerHTML = "FPS Average: " + Math.round(1000 * this.frames / this.  totalTime) + " Current: " + Math.round(1000 * this.updateFrames / this.updateTime);
        this.updateTime = 0; // reset time
        this.updateFrames = 0; // reset frames
    }

};

Clock.init('canvas');
</script>

<div style="text-align:center;margin:50px 0; font:normal 14px/24px 'MicroSoft YaHei';">
<p>适用浏览器：360、FireFox、Chrome、Safari、Opera、傲游、搜狗、世界之窗. 不支持IE8及以下浏览器。</p>
<p>来源：<a href="http://down.admin5.com/" target="_blank">A5源码</a></p>
</div>
</body>
</html>

